package com.itcast.activiti.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.itcast.activiti.cmd.GetProcessCmd;
import com.itcast.activiti.cmd.GetProcessDefinitionCacheEntryCmd;
import com.itcast.activiti.constant.NodeKeyTag;
import com.itcast.activiti.domain.ApproveNode;
import com.itcast.activiti.domain.StartProcessInstance;
import com.itcast.activiti.domain.TaskStart;
import com.itcast.activiti.domain.UserInfo;
import com.itcast.activiti.entity.ApproveAddSign;
import com.itcast.activiti.entity.ApproveDefinition;
import com.itcast.activiti.event.NotifyTaskApplyEvent;
import com.itcast.activiti.service.*;
import com.itcast.activiti.utils.ActivitiPage;
import com.itcast.activiti.utils.ApproveUtils;
import com.itcast.activiti.utils.TimeUtils;
import com.itcast.activiti.utils.VariablesUtil;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.engine.ManagementService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.delegate.BaseExecutionListener;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.persistence.deploy.ProcessDefinitionCacheEntry;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceBuilder;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class IProcessRuntimeServiceImpl implements IProcessRuntimeService {
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private IHistoryService iHistoryService;
    @Autowired
    private ITaskRuntimeService iTaskRuntimeService;
    @Autowired
    private IApproveDefinitionService iApproveDefinitionService;
    @Autowired
    private ApproveUtils approveUtils;
    @Autowired
    private ApproveJsonService approveJsonService;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private ProcessEngine processEngine;
    @Autowired
    private IActIdUserService iActIdUserService;
    @Autowired
    private IApproveAddSignService IApproveAddSignService;

    @Override
    public Boolean checkApprove(StartProcessInstance startProcessInstance) {
        String processDefinitionKey = startProcessInstance.getProcessDefinitionKey();
        Assert.hasText(processDefinitionKey, "processDefinitionKey is empty");
        String tenantId = StringUtils.hasText(startProcessInstance.getTenantId()) ? startProcessInstance.getTenantId() : "PMIS";
        String definitionId = processDefinitionKey.replace(NodeKeyTag.PROCESS_PREFIX, "");
        String userId = startProcessInstance.getUserName();

        ApproveDefinition approveDefinition = iApproveDefinitionService.getApproveDefinitionByDefinitionIdAndTenantId(definitionId, tenantId);
        Assert.notNull(approveDefinition, "approveDefinition is empty");
        if (!approveUtils.isAllow(approveDefinition.getUserRange(), userId, definitionId)) {
            throw new RuntimeException("当前用户无发起该流程审批的权限！");
        }
        // 计算当前审批实例，需要走过的流程节点
        List<ApproveNode> approveNodeList = approveJsonService.getApproveNodesFromJson(approveDefinition, userId, false);
        // 发起流程时入参变量
        for (int i = 0; i < approveNodeList.size(); i++) {
            ApproveNode approveNode = approveNodeList.get(i);
            Set<String> userIds = new HashSet<>();
            if (approveNode.getOughtUsers().size() != 0 && approveNode.getOughtUsers() != null) {
                for (UserInfo oughtUser : approveNode.getOughtUsers()) {
                    userIds.add(oughtUser.getUserId());
                }
            }

            if (i >= 1 && CollectionUtils.isEmpty(userIds)) {
                log.info("节点 [{}] 未配置{}的审批人！", approveNode.getName(), userId);
                throw new RuntimeException("无法发起此流程，原因：未配置当前用户的审批人");
            }

            if (StringUtils.hasText(userId)) {
                if (i == 1) {
                    if (!userIds.contains(userId)) {
                        throw new RuntimeException("当前用户无发起该流程审批的权限！");
                    }
                }
            }
        }
        return Boolean.TRUE;
    }

    @Override
    public Map<String, Object> startProcessInstance(StartProcessInstance startProcessInstance) {
        if (!StringUtils.hasText(startProcessInstance.getProcessDefinitionId()) && !StringUtils.hasText(startProcessInstance.getProcessDefinitionKey())) {
            Assert.hasText(null, "processDefinitionId and processDefinitionKey not meanwhile is empty");
        }
        String processDefinitionId = startProcessInstance.getProcessDefinitionId();
        String processDefinitionKey = startProcessInstance.getProcessDefinitionKey();
        String businessKey = startProcessInstance.getBusinessKey();
        String name = startProcessInstance.getName();
        Map<String, Object> variables = startProcessInstance.getVariables();
        Assert.hasText(name, "name is empty");
        Assert.hasText(businessKey, "businessKey is empty");
        HistoricProcessInstance historicProcessInstance = iHistoryService.getHistoricProcessInstanceByBusinessKey(businessKey, startProcessInstance.getTenantId());
        Assert.isNull(historicProcessInstance, "businessKey is exist");
        ProcessInstanceBuilder processInstanceBuilder = runtimeService.createProcessInstanceBuilder().businessKey(businessKey).name(name);
        if (StringUtils.hasText(processDefinitionId)) {
            processInstanceBuilder.processDefinitionId(processDefinitionId);
        }
        if (StringUtils.hasText(processDefinitionKey)) {
            processInstanceBuilder.processDefinitionKey(processDefinitionKey);
        }
        if (variables != null && !variables.equals("{}")) {
            processInstanceBuilder.variables(variables);
        }
        processInstanceBuilder.tenantId(StringUtils.hasText(startProcessInstance.getTenantId()) ? startProcessInstance.getTenantId() : "ITCAST_PMIS");
        ProcessInstance processInstance = processInstanceBuilder.start();
        if (processInstance != null) {
            TaskStart taskStart = new TaskStart();
            taskStart.setEvent(BaseExecutionListener.EVENTNAME_START);
            taskStart.setPassbackParam(startProcessInstance.getPassbackParam());
            taskStart.setAutoNextComplete(startProcessInstance.getAutoNextComplete());
            applicationContext.publishEvent(new NotifyTaskApplyEvent(this, StringUtils.hasText(startProcessInstance.getTenantId()) ? startProcessInstance.getTenantId() : "PMIS", businessKey, startProcessInstance.getUserName(), taskStart, null));
            return startProcessInstanceResultMap(processInstance);
        }
        return null;
    }

    @Override
    public Map<String, Object> startProcessApply(StartProcessInstance startProcessInstance) {
        String processDefinitionKey = startProcessInstance.getProcessDefinitionKey();
        Assert.hasText(processDefinitionKey, "processDefinitionKey is empty");
        String tenantId = StringUtils.hasText(startProcessInstance.getTenantId()) ? startProcessInstance.getTenantId() : "PMIS";
        String definitionId = processDefinitionKey.replace(NodeKeyTag.PROCESS_PREFIX, "");
        String userId = startProcessInstance.getUserName();

        ApproveDefinition approveDefinition = iApproveDefinitionService.getApproveDefinitionByDefinitionIdAndTenantId(definitionId, tenantId);
        Assert.notNull(approveDefinition, "approveDefinition is empty");
        if (!approveUtils.isAllow(approveDefinition.getUserRange(), userId, definitionId)) {
            throw new RuntimeException("当前用户无发起该流程审批的权限！");
        }
        // 计算当前审批实例，需要走过的流程节点
        List<ApproveNode> approveNodeList = approveJsonService.getApproveNodesFromJson(approveDefinition, userId, false);
        // 发起流程时入参变量
        HashMap<String, Object> variables = new HashMap<>();
//        for (ApproveNode approveNode : approveNodeList) {
//            Set<String> userIds = new HashSet<>();
//            if (approveNode.getOughtUsers().size() != 0 && approveNode.getOughtUsers() != null) {
//                for (UserInfo oughtUser : approveNode.getOughtUsers()) {
//                    userIds.add(oughtUser.getUserId());
//                }
//            }
//            variables.put(NodeKeyTag.USERS_PREFIX + approveNode.getNodeKey(), userIds);
//        }
        for (int i = 0; i < approveNodeList.size(); i++) {
            ApproveNode approveNode = approveNodeList.get(i);
            Set<String> userIds = new HashSet<>();
            if (approveNode.getOughtUsers().size() != 0 && approveNode.getOughtUsers() != null) {
                for (UserInfo oughtUser : approveNode.getOughtUsers()) {
                    userIds.add(oughtUser.getUserId());
                }
            }

            if (i >= 1 && CollectionUtils.isEmpty(userIds)) {
                log.info("节点 [{}] 未配置{}的审批人！", approveNode.getName(), userId);
                throw new RuntimeException("无法发起此流程，原因：未配置当前用户的审批人");
            }

            if (StringUtils.hasText(userId)) {
                if (i == 1) {
                    if (!userIds.contains(userId)) {
                        throw new RuntimeException("当前用户无发起该流程审批的权限！");
                    }
                }
            }
            variables.put(NodeKeyTag.USERS_PREFIX + approveNode.getNodeKey(), userIds);
        }
        if (startProcessInstance.getVariables() != null) {
            variables.putAll(startProcessInstance.getVariables());
        }
        startProcessInstance.setProcessDefinitionKey(processDefinitionKey);
//        startProcessInstance.setTenantId(approveDefinition.getTenantId());
        startProcessInstance.setVariables(variables);
        Map<String, Object> map = this.startProcessInstance(startProcessInstance);
        // 如果是知会节点，则需要自动完成并跳过
        iTaskRuntimeService.listJudgeAndComplateNotifyTask(iTaskRuntimeService.listTaskByProcessInstanceId(map.get("id").toString()));
        return map;
    }

    @Override
    public ProcessInstance getProcessInstanceByBusinessKey(String businessKey, String tenantId) {
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        processInstanceQuery.processInstanceBusinessKey(businessKey);
        if (StringUtils.hasText(tenantId)) {
            processInstanceQuery.processInstanceTenantId(tenantId);
        }
        return processInstanceQuery.singleResult();
    }

    @Override
    public ProcessInstance getProcessInstanceByProcessInstanceId(String processInstanceId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                                                        .processInstanceId(processInstanceId).singleResult();
        return processInstance;
    }

    @Override
    public Set<String> getBusinessKeys(Set<String> processInstanceIds) {
        List<ProcessInstance> list = runtimeService.createProcessInstanceQuery().processInstanceIds(processInstanceIds).list();
        if (list == null || list.isEmpty()) {
            return null;
        }
        return list.stream().map(ProcessInstance::getBusinessKey).collect(Collectors.toSet());
    }

    @Override
    public ActivitiPage listProcessInstancePage(ActivitiPage activitiPage) {
        JSONObject condition = activitiPage.getCondition();
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        if (StringUtils.hasText(condition.getString(StartProcessInstance.Fields.businessKey))) {
            processInstanceQuery.processInstanceBusinessKey(condition.getString(StartProcessInstance.Fields.businessKey));
        }
        if (StringUtils.hasText(condition.getString(StartProcessInstance.Fields.tenantId))) {
            processInstanceQuery.processInstanceTenantId(condition.getString(StartProcessInstance.Fields.tenantId));
        }
        Integer startIndex = Math.toIntExact(activitiPage.getCurrent());
        Integer maxItems = Math.toIntExact(activitiPage.getSize());
        List<ProcessInstance> processInstanceList = processInstanceQuery.listPage(startIndex, maxItems);
        List<Map<String, Object>> resultList = new ArrayList<>();
        for (ProcessInstance processInstance : processInstanceList) {
            Map<String, Object> stringObjectMap = startProcessInstanceResultMap(processInstance);
            resultList.add(stringObjectMap);
        }
        activitiPage.setTotal(processInstanceQuery.count());
        activitiPage.setRecords(resultList);
        return activitiPage;
    }

    @Override
    public Map<String, Object> processSuspend(String businessKey, String tenantId) {
        ProcessInstance processInstance = getProcessInstanceByBusinessKey(businessKey, tenantId);
        Assert.notNull(processInstance, businessKey + " is not process instance");
        String processInstanceId = processInstance.getId();
        boolean isSuspended = processInstance.isSuspended();
        if (!processInstance.isSuspended()) {
            runtimeService.suspendProcessInstanceById(processInstanceId);
            isSuspended = !isSuspended;
        }
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("id", processInstanceId);
        resultMap.put("processDefinitionId", processInstance.getProcessDefinitionId());
        resultMap.put("isSuspended", isSuspended);
        return resultMap;
    }

    @Override
    public Map<String, Object> processActivate(String businessKey, String tenantId) {
        ProcessInstance processInstance = getProcessInstanceByBusinessKey(businessKey, tenantId);
        Assert.notNull(processInstance, businessKey + " is not process instance");
        String processInstanceId = processInstance.getId();
        runtimeService.activateProcessInstanceById(processInstanceId);
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("id", processInstance.getId());
        resultMap.put("processDefinitionId", processInstance.getProcessDefinitionId());
        resultMap.put("isSuspended", !processInstance.isSuspended());
        return resultMap;
    }

    @Override
    public Execution getExecution(String executionId) {
        return runtimeService.createExecutionQuery().executionId(executionId).singleResult();
    }

    @Override
    public ActivitiPage tasks(ActivitiPage activitiPage) {
        ActivitiPage page = iHistoryService.listHistoricTaskInstancePage(activitiPage);
        List records = page.getRecords();
        Map<String, Object> map = (HashMap<String, Object>) records.get(records.size() - 1);
        String taskDefinitionKey = (String) map.get("taskDefinitionKey");
        String processDefinitionId = (String) map.get("processDefinitionId");
        String executionId = (String) map.get("executionId");
        Execution execution = getExecution(executionId);
        if (execution == null) {
            return page;
        }
        Map<String, Object> variables = processEngine.getRuntimeService().getVariables(executionId);
        ManagementService managementService = processEngine.getManagementService();
        managementService.executeCommand(new GetProcessCmd(processDefinitionId));
        ProcessDefinitionCacheEntry processDefinitionCacheEntry = managementService.executeCommand(new GetProcessDefinitionCacheEntryCmd(processDefinitionId, processEngine));
        Process process = processDefinitionCacheEntry.getProcess();
        Map<String, List<SequenceFlow>> collect = process.getFlowElements().stream().filter(v -> v instanceof SequenceFlow).map(v -> (SequenceFlow) v).collect(Collectors.groupingBy(SequenceFlow::getSourceRef));
        List<SequenceFlow> sequenceFlows = collect.get(taskDefinitionKey);
        List<ApproveAddSign> approveAddSigns = IApproveAddSignService.findByProcDefId(processDefinitionId);
        Map<String, ApproveAddSign> approveAddSignMap = approveAddSigns.stream().collect(Collectors.toMap(ApproveAddSign::getId, t -> t));
        while (!CollectionUtils.isEmpty(sequenceFlows) && sequenceFlows.size() == 1) {
            SequenceFlow sequenceFlow = sequenceFlows.get(0);
            FlowElement flowElement = process.getFlowElement(sequenceFlow.getTargetRef());
            if (flowElement instanceof EndEvent) {
                break;
            }
            if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;
                Map<String, Object> stringObjectMap = new HashMap<>();
                stringObjectMap.put("taskDefinitionKey", userTask.getId());
                stringObjectMap.put("name", userTask.getName());
                ApproveAddSign approveAddSign = approveAddSignMap.get(userTask.getId().replace(NodeKeyTag.USERTASK_PREFIX, ""));
                stringObjectMap.put("createdBy", Optional.ofNullable(approveAddSign).map(ApproveAddSign::getCreatedBy).orElse(""));
                stringObjectMap.put("multiApproveMode", userTask.hasMultiInstanceLoopCharacteristics() ? 1 : 2);
                JSONArray assignees = getAssignee(variables, userTask, stringObjectMap);
                Set<String> emails = new TreeSet<>();
                Set<String> userNames = new TreeSet<>();
                assignees.forEach(value -> {
                    emails.add(value.toString());
                    String userName = iActIdUserService.getUserNameByemail(value.toString());
                    if (StringUtils.hasText(userName)) {
                        userNames.add(userName);
                    } else {
                        String userNameById = iActIdUserService.getUserNameById(value.toString());
                        userNames.add(StringUtils.hasText(userNameById) ? userNameById : VariablesUtil.regExp(value.toString()));
                    }
                });
                stringObjectMap.put("email", String.join(",", emails));
                stringObjectMap.put("assignee", String.join(",", userNames));
                records.add(stringObjectMap);
                sequenceFlows = collect.get(sequenceFlow.getTargetRef());
            } else {
                break;
            }
        }
        return page;
    }

    @Override
    public Set<String> assignees(String businessKey, String tenantId) {
        ProcessInstance processInstance = getProcessInstanceByBusinessKey(businessKey, tenantId);
        Assert.notNull(processInstance, businessKey + " is not process instance");
        Map<String, Object> variables = processEngine.getRuntimeService().getVariables(processInstance.getId());
        Set<String> emails = new HashSet<>();
        for (Map.Entry<String, Object> stringObjectEntry : variables.entrySet()) {
            if (stringObjectEntry.getKey().startsWith(NodeKeyTag.USERS_PREFIX)) {
                List<String> objects = JSON.parseArray(stringObjectEntry.getValue().toString(), String.class);
                emails.addAll(objects);
            }
        }
        return emails;
    }

    private JSONArray getAssignee(Map<String, Object> variables, UserTask userTask, Map<String, Object> stringObjectMap) {
        if (userTask.hasMultiInstanceLoopCharacteristics()) {
            MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics();
            String inputDataItem = loopCharacteristics.getInputDataItem();
            String candidateUser = VariablesUtil.regExp(inputDataItem);
            return JSON.parseArray(variables.get(candidateUser).toString());
        } else {
            String candidateUser = VariablesUtil.regExp(userTask.getCandidateUsers().get(0));
            return JSON.parseArray(variables.get(candidateUser).toString());
        }
    }

    @Override
    public Map<String, Object> startProcessInstanceResultMap(ProcessInstance processInstance) {
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("id", processInstance.getId());
        resultMap.put("name", processInstance.getName());
        resultMap.put("deploymentId", processInstance.getDeploymentId());
        resultMap.put("startTime", TimeUtils.format(processInstance.getStartTime()));
        resultMap.put("businessKey", processInstance.getBusinessKey());
        resultMap.put("startUserId", processInstance.getStartUserId());
        resultMap.put("tenantId", processInstance.getTenantId());
        resultMap.put("isSuspended", processInstance.isSuspended());
        resultMap.put("description", processInstance.getDescription());
        resultMap.put("processDefinitionId", processInstance.getProcessDefinitionId());
        resultMap.put("processDefinitionKey", processInstance.getProcessDefinitionKey());
        resultMap.put("processDefinitionName", processInstance.getProcessDefinitionName());
        resultMap.put("processDefinitionVersion", processInstance.getProcessDefinitionVersion());
        resultMap.put("activityId", processInstance.getActivityId());
        return resultMap;
    }
}
